﻿using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;

namespace Services
{
    public class CameraConsumer : Microsoft.Xna.Framework.DrawableGameComponent
    {
        protected Vector3 EyePosition { get { return Vector3.Transform(-view.Translation, Matrix.Transpose(view)); } }
        protected Microsoft.Xna.Framework.Matrix view;
        protected Microsoft.Xna.Framework.Matrix projection;
        protected float max_depth;
        public void SetCamera(Microsoft.Xna.Framework.Matrix view, Microsoft.Xna.Framework.Matrix projection, float max_depth)
        {
            this.view = view;
            this.projection = projection;
            this.max_depth = max_depth;
        }

        public CameraConsumer(Game game) : base(game) { }
        public override void Initialize()
        {
            var camera_provider = Game.Services.GetService(typeof(CameraProvider)) as CameraProvider;
            camera_provider.AddCameraConsumer(this);

            base.Initialize();
        }
    }

    public class CameraProvider : Microsoft.Xna.Framework.DrawableGameComponent
    {
        public CameraProvider(Game game)
            : base(game)
        {
            game.Services.AddService(typeof(CameraProvider), this);
        }

        /// Alla chiusura facciamo pulizia dei servizi istanziati
        protected override void Dispose(bool disposing)
        {
            Game.Services.RemoveService(typeof(CameraProvider));
            base.Dispose(disposing);
        }

        public Vector3 EyePosition { get { return Vector3.Transform(-view.Translation, Matrix.Transpose(view)); } }
        public Matrix view = Matrix.CreateLookAt(Vector3.Forward * 3.0f + Vector3.Up * 10.0f, Vector3.Zero, Vector3.Up);
        public Matrix projection;

        /// <summary>
        /// Misura in float della diagonale dell'attuale ViewPort
        /// </summary>
        public float diagonalSize;
        /// <summary>
        /// Il punto in alto a sinistra dell'attuale ViewPort 
        /// </summary>
        public Vector3 upperRight;
        /// <summary>
        /// Il punto in basso a destra dell'attuale ViewPort
        /// </summary>
        public Vector3 lowerLeft;

        public float max_depth = 200.0f;

        /// <summary>
        /// Converte un punto nello spazio "normale" (800x480) nel corrispettivo punto nella Viewport.
        /// </summary>
        protected Vector3 fromPointToVieportPoint(Vector2 point)
        {

            Vector3 sca, posi;
            Quaternion ori;
            Matrix.Transpose(view).Decompose(out sca, out ori, out posi);

            var touch_ray = GraphicsDevice.Viewport.GetRay(point, view, projection);
            var plane = new Plane(new Vector3(1, 0, 0), new Vector3(-1, 0, 0), new Vector3(0, 0, 1));
            var dist = touch_ray.Intersects(plane);
            return (touch_ray.Position + dist.Value * touch_ray.Direction);
        }

        protected override void LoadContent()
        {
            projection = Matrix.CreatePerspectiveFieldOfView(1.5f * GraphicsDevice.Viewport.Width / 800.0f,
                GraphicsDevice.Viewport.Width / (float)GraphicsDevice.Viewport.Height, 0.1f, max_depth);

            diagonalSize = Vector3.Distance(fromPointToVieportPoint(new Vector2(1, 1)), fromPointToVieportPoint(new Vector2(799, 479)));
            lowerLeft = fromPointToVieportPoint(new Vector2(799, 479));
            upperRight = new Vector3(-lowerLeft.X, 0, -lowerLeft.Z);

            base.LoadContent();
        }

        List<CameraConsumer> camera_consumers = new List<CameraConsumer>(20);
        public void AddCameraConsumer(CameraConsumer consumer)
        {
            camera_consumers.Add(consumer);
        }

        public void RemoveCameraConsumer(CameraConsumer consumer)
        {
            camera_consumers.Remove(consumer);
        }

        public override void Update(GameTime gameTime)
        {
            for (int i = 0; i < camera_consumers.Count; i++)
                camera_consumers[i].SetCamera(view, projection, max_depth);

            base.Update(gameTime);
        }
    }
}
